#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>


#include "r_socket_ipv4_udp_multicast.h"
#include "r_socket_obj_manager.h"
#include "r_socket_common.h"

#include "utilities/string_operator.h"
#include "utilities/r_logger.h"

namespace eniac
{

r_socket_proxy_ret_t r_socket_ipv4_udp_multicast::set_socket_property(const r_socket::ptr socket_obj, r_socket_property_type type, void* property, size_t property_size)
{
    if (nullptr == socket_obj.get())
    {
        return r_socket_proxy_ret_t::NULL_PTR;
    }

    std::lock_guard<std::mutex> lg{socket_obj->m_mutex};

    if (type == r_socket_property_type::SOCKET_PROPERTY_MULTICAST_ADDRESS)
    {
        return set_socket_multicast_address(socket_obj, property, property_size);
    }

    if (type == r_socket_property_type::SOCKET_PROPERTY_MULTICAST_PORT)
    {
        return set_socket_multicast_port(socket_obj, property, property_size);
    }

    return r_socket_proxy_ret_t::INVALID_PROPERTY_TYPE_FOR_THE_SOCKET;
}

r_socket_proxy_ret_t r_socket_ipv4_udp_multicast::set_socket_multicast_address(const r_socket::ptr socket_obj, void* property, size_t property_size)
{
    if ( property_size < strlen("0.0.0.0") ||
         property_size > strlen("000.000.000.000") )
    {
        return r_socket_proxy_ret_t::INVALID_PROPERTY_LENGTH;
    }

    std::string ip_str = reinterpret_cast<char*>(property);

    if (false == is_valid_ipv4_ip(ip_str) )
    {
        return r_socket_proxy_ret_t::INVALID_PROPERTY_VALUE;
    }

    socket_obj->m_multicast_address = ip_str;

    return r_socket_proxy_ret_t::OK;
}

r_socket_proxy_ret_t r_socket_ipv4_udp_multicast::set_socket_multicast_port(const r_socket::ptr socket_obj, void* property, size_t property_size)
{
    if (property_size != sizeof(socket_obj->m_own_port))
    {
        
    }

    if (INVALID_SOCKET_PORT == *reinterpret_cast<uint16_t*>(property))
    {
        return r_socket_proxy_ret_t::INVALID_PROPERTY_VALUE;
    }

    socket_obj->m_multicast_port = *reinterpret_cast<uint16_t*>(property);

    return r_socket_proxy_ret_t::OK;
}


r_socket_proxy_ret_t r_socket_ipv4_udp_multicast::start_socket(const r_socket::ptr socket_obj)
{
    if (nullptr == socket_obj.get())
    {
        return r_socket_proxy_ret_t::NULL_PTR;
    }

    std::lock_guard<std::mutex> lg{socket_obj->m_mutex};

    if (socket_obj->m_multicast_address.empty())
    {
        return r_socket_proxy_ret_t::MULTICAST_ADDRESS_HAS_NOT_BEEN_SET_YET;
    }

    if (socket_obj->m_multicast_port == INVALID_SOCKET_PORT)
    {
        return r_socket_proxy_ret_t::MULTICAST_PORT_HAS_NOT_BEEN_SET_YET;
    }

    if (socket_obj->m_status == r_socket_status_t::R_SOCKET_STATUS_STARTED)
    {
        return r_socket_proxy_ret_t::SOCKET_OBJ_HAS_BEEN_STARTED;
    }

    if (socket_obj->m_socket_fd != INVALID_SOCKET_FD)
    {
        return r_socket_proxy_ret_t::SOCKET_FD_HAS_BEEN_SET;
    }

    socket_obj->m_socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (socket_obj->m_socket_fd < 0)
    {
         return r_socket_proxy_ret_t::TO_CREATE_SOCKET_FD_IS_FAILED;
    }

    int broadcast_enable = 1;
    if (setsockopt(socket_obj->m_socket_fd, SOL_SOCKET, SO_BROADCAST, 
                    &broadcast_enable, sizeof(broadcast_enable)) < 0)
    {
        close(socket_obj->m_socket_fd);
        socket_obj->m_socket_fd = INVALID_SOCKET_FD;
        return r_socket_proxy_ret_t::TO_SET_SOCKET_BROADCAST_IS_FAILED;
    }

    int reuse_addr = 1;
    if (setsockopt(socket_obj->m_socket_fd, SOL_SOCKET, SO_REUSEADDR,
                    &reuse_addr, sizeof(reuse_addr)) < 0)
    {
        close(socket_obj->m_socket_fd);
        socket_obj->m_socket_fd = INVALID_SOCKET_FD;
        return r_socket_proxy_ret_t::TO_SET_SOCKET_REUSE_IS_FAILED;
    }

    int ttl = 1;
    if (setsockopt(socket_obj->m_socket_fd, IPPROTO_IP, IP_MULTICAST_TTL, 
                    &ttl, sizeof(ttl)) < 0)
    {
        close(socket_obj->m_socket_fd);
        socket_obj->m_socket_fd = INVALID_SOCKET_FD;
        return r_socket_proxy_ret_t::TO_SET_SOCKET_TTL_IS_FAILED;
    }

    int loop = 0;
    if (setsockopt(socket_obj->m_socket_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0)
    {
        close(socket_obj->m_socket_fd);
        socket_obj->m_socket_fd = INVALID_SOCKET_FD;
        return r_socket_proxy_ret_t::TO_SET_SOCKET_NO_LOOP_IS_FAILED;
    }

    struct sockaddr_in recv_addr = {0};
    socklen_t addr_len = sizeof(struct sockaddr);
    recv_addr.sin_family = AF_INET;
    recv_addr.sin_port = htons(socket_obj->m_multicast_port);
    recv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 
    if(bind(socket_obj->m_socket_fd, (struct sockaddr*)&recv_addr, addr_len) < 0)
    {
        LOG_DBG() << "To bind address is failed " << " errno " <<  errno <<  "  strerror(errno) " << strerror(errno);
        close(socket_obj->m_socket_fd);
        socket_obj->m_socket_fd = INVALID_SOCKET_FD;
        return r_socket_proxy_ret_t::TO_SET_SOCKET_BROADCAST_ADDR_IS_FAILED;
    }

    struct ip_mreq mreq;
    mreq.imr_multiaddr.s_addr = ipv4_to_net_int(socket_obj->m_multicast_address);
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
    if (setsockopt(socket_obj->m_socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, 
                    &mreq, sizeof(mreq)) < 0)
    {
        LOG_DBG() << "To set broadcast address is failed " << " errno " <<  errno <<  "  strerror(errno) " << strerror(errno);
        close(socket_obj->m_socket_fd);
        socket_obj->m_socket_fd = INVALID_SOCKET_FD;
        return r_socket_proxy_ret_t::TO_SET_SOCKET_BROADCAST_ADDR_IS_FAILED;
    }

    socket_obj->m_status = r_socket_status_t::R_SOCKET_STATUS_STARTED;
    r_socket_obj_manager::get_instance()->update_socket_obj(socket_obj);
    return r_socket_proxy_ret_t::OK;
}

void r_socket_ipv4_udp_multicast::close_socket(const r_socket::ptr socket_obj)
{
    if (nullptr == socket_obj.get())
    {
        return;
    }

    if (socket_obj->m_socket_fd == INVALID_SOCKET_FD)
    {
        return;
    }

    std::lock_guard<std::mutex> lg{socket_obj->m_mutex};

    close(socket_obj->m_socket_fd);
    socket_obj->m_socket_fd = INVALID_SOCKET_FD;
    socket_obj->m_status = r_socket_status_t::R_SOCKET_STATUS_STOPPING;
    r_socket_obj_manager::get_instance()->update_socket_obj(socket_obj);
}

r_socket_proxy_ret_t r_socket_ipv4_udp_multicast::read_message(const r_socket::ptr socket_obj, r_socket_message::ptr& message)
{
    if (nullptr == socket_obj.get())
    {
        LOG_WRN() << "Socket object is NULL";
        return r_socket_proxy_ret_t::NULL_PTR;
    }

    if (socket_obj->m_socket_fd == INVALID_SOCKET_FD)
    {
        LOG_WRN() << "Socket FD is INVALID";
        return r_socket_proxy_ret_t::INVALID_SOCKET_FD;
    }

    uint64_t readable_bytes = r_socket_common::get_readable_bytes_length(socket_obj->m_socket_fd);
    if (0 == readable_bytes)
    {
        LOG_WRN() << "No data can be read, the remote socket has closed";
        return r_socket_proxy_ret_t::NO_DATA_CAN_BE_READ;
    }

    message = r_socket_message::create_obj();
    message->m_socket_id = socket_obj->m_socket_id;
    message->m_payload.resize(readable_bytes);

    // struct sockaddr_in group_addr;
    // memset(&group_addr, 0, sizeof(group_addr));
    // group_addr.sin_family = AF_INET;
    // group_addr.sin_port = htons(socket_obj->m_multicast_port);
    // group_addr.sin_addr.s_addr = ipv4_to_net_int(socket_obj->m_multicast_address);
    struct sockaddr_in sender_addr;
    socklen_t sender_len = sizeof(sender_addr);

    ssize_t recv_bytes = recvfrom(socket_obj->m_socket_fd, message->m_payload.data(), message->m_payload.size(), 0,
                         (struct sockaddr*)&sender_addr, &sender_len);
    if (recv_bytes <= 0)
    {
        LOG_WRN() << "No data can be read, the remote socket has closed(read)";
        message.reset();
        return r_socket_proxy_ret_t::TO_READ_DATA_FROME_SOCKET_IS_FAILED;
    }

    LOG_DBG() << "Read " << message->m_payload.size() << " bytes";
    return r_socket_proxy_ret_t::OK;
}

r_socket_proxy_ret_t r_socket_ipv4_udp_multicast::send_message(const r_socket::ptr socket_obj, const std::vector<uint8_t>& message)
{
    if (nullptr == socket_obj.get())
    {
        return r_socket_proxy_ret_t::NULL_PTR;
    }

    if (socket_obj->m_socket_fd == INVALID_SOCKET_FD)
    {
        return r_socket_proxy_ret_t::INVALID_SOCKET_FD;
    }

    if (0 == message.size())
    {
        return r_socket_proxy_ret_t::NO_DATA_CAN_BE_SENT;
    }

    std::lock_guard<std::mutex> lg{socket_obj->m_mutex};

    struct sockaddr_in group_addr;
    memset(&group_addr, 0, sizeof(group_addr));
    group_addr.sin_family = AF_INET;
    group_addr.sin_port = htons(socket_obj->m_multicast_port);
    group_addr.sin_addr.s_addr = ipv4_to_net_int(socket_obj->m_multicast_address);

    ssize_t send_num = sendto(
                        socket_obj->m_socket_fd,
                        message.data(), 
                        message.size(), 0,
                        (struct sockaddr*)&group_addr, 
                        sizeof(group_addr));
    if (send_num != message.size())
    {
        //TODO Make Sure ALL Data is sent
        return r_socket_proxy_ret_t::TO_SEND_DATA_IS_FAILED;
    }

    LOG_DBG() << "To send Message is success";
    return r_socket_proxy_ret_t::OK;
}


}